package com.example.springdemo.framework.servlet;

import com.example.springdemo.framework.annotation.GPAutowired;
import com.example.springdemo.framework.annotation.GPController;
import com.example.springdemo.framework.annotation.GPRequestMapping;
import com.example.springdemo.framework.annotation.GPService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * 讲了 IoC,DI,SpringMVC
 */
public class GPDispatcherServlet extends HttpServlet {

    private Properties contextConfig = new Properties();
    private List<String> classNames = new ArrayList<>();
    //IoC容器
    private Map<String, Object> ioc = new HashMap<>();
    private Map<String, Method> handlerMapping = new HashMap<>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6.调用
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 : Exception Detail: " + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        String url = req.getRequestURI();//请求的路径
        String contextPath = req.getContextPath();//相对路径
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 Not Found!");

            return;
        }

        Map<String, String[]> params = req.getParameterMap();

        Method method = this.handlerMapping.get(url);
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());//类名
        method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

        //1.加载配置文件web.xml
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //3.初始化所有相关联的类，并且将其保存在IOC容器里面
        doInstance();

        //4.完成依赖注入（把加了@Autowired注解的字段赋值）
        doAutowired();

        //Spring 和核心功能已经完成 IOC、DI

        //5.初始化HandlerMapping，将URL和Method进行关联
        doInitHandlerMapping();

        System.out.println("GP Spirng framewore is intit.");

    }

    private void doLoadConfig(String contextConfigLocation) {
        //得到文件流
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doScanner(String scanPackage) {
        //相当于：/com/example/springdemo/demo，包名在classpath下就是文件夹
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/")); // 正则转义\\.
        File classPath = new File(url.getFile());

        for (File file : classPath.listFiles()
        ) {
            //是文件夹要递归
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                //不都是class文件
                if (!file.getName().endsWith(".class")) {
                    continue;
                }

                //替换为了去掉后缀，就得到了完整的className，为了后边反射来创建对象
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }

    }

    //初始化所有相关联的类，并且将其保存在IOC容器里面
    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }

        try {
            for (String className :
                    classNames) {
                Class clazz = Class.forName(className);

                //不是所有类都初始化（为了后边依赖注入），仅加了两个注解的
                if (clazz.isAnnotationPresent(GPController.class)) { //Controller没接口
                    //beanName默认是类名首字母小写
                    //<bean id="" class="">
                    String beanName = toLowerFirstCase(clazz.getSimpleName());//这得到的是类名首字母大写(java编码规范首字母是大写)，自己改成小写
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(GPService.class)) { //Service可能有接口
                    //1、默认的类名首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());//这得到的是类名首字母大写(java编码规范首字母是大写)，自己改成小写
                    //2、优先使用自定义命名
                    GPService service = (GPService) clazz.getAnnotation(GPService.class);
                    if (!"".equals(service.value())) {
                        beanName = service.value();
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    //3、接口是不能直接实例化的，要实例化它的实现类
                    for (Class i :
                            clazz.getInterfaces()) {
                        //接口要有两个实现实的话报异常
                        if (ioc.containsKey(i.getName())) {
                            throw new Exception("The beanName is exists!!");
                        }

                        ioc.put(i.getName(), instance);
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //首字母变小写
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry :
                ioc.entrySet()) {
            //注入的意思就是把所有的IOC容器中加了@Autowired注解的字段赋值
            //包含私有字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field :
                    fields) {
                //仅加了GPAutowired注解的才赋值
                if (!field.isAnnotationPresent(GPAutowired.class)) {
                    continue;
                }

                GPAutowired autowired = field.getAnnotation(GPAutowired.class);
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
                    beanName = field.getType().getName();
                }

                //如果这个字段是私有字段的话，那么要强制访问
                //这个方法：不管你愿不愿意，直接强吻
                field.setAccessible(true);//作用就是让我们在用反射时访问私有变量
                try {
                    //动态给这个字段赋值。向对象的这个Field属性设置新值value（向对象entry.getValue()的属性field设置新值ioc.get(beanName)）
                    field.set(entry.getValue(), ioc.get(beanName)); //TODO ?????这句没看懂,ok了:（向对象entry.getValue()的属性field设置新值ioc.get(beanName)）
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 初始化HandlerMapping，将URL和Method进行关联
    private void doInitHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry :
                ioc.entrySet()) {
            Class clazz = entry.getValue().getClass();

            //只针对controller
            if (!clazz.isAnnotationPresent(GPController.class)) {
                continue;
            }

            //类上也有GPRequestMapping注解
            String baseUrl = "";
            if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
                GPRequestMapping baseRequestMapping = (GPRequestMapping) clazz.getAnnotation(GPRequestMapping.class);
                baseUrl = baseRequestMapping.value();
            }

            //方法上的GPRequestMapping注解
            for (Method method :
                    clazz.getMethods()) {
                if (!method.isAnnotationPresent(GPRequestMapping.class)) {
                    continue;
                }

                GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);

                //处理url没斜杠的情况，要是多了用正则处理掉
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println("Mapped: " + url + ", " + method);
            }
        }
    }
}
